안드로이드(Android) - 리스트뷰(ListView) :: 안드로이드 설치 및 개발[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

안드로이드 설치 및 개발
[1]
등록일:2018-07-15 08:25:52 (0%)
작성자:
제목:안드로이드(Android) - 리스트뷰(ListView)

리스트 뷰의 원리 (중요)

리스트 뷰의 원리를 이해해야 코드를 이해할 수 있습니다.

약 100개의 정보가 담긴 List가 있다고 가정했을 때

화면에서 6개를 보여주고 스크롤을 이용해 아래로 내리면서 총 100개의 정보를 볼 수 있다고 하겠습니다.

화면상에 6개만 보입니다. 나머지 94개는 아래 있다고 생각할 수 있지만 아닙니다.

ListView는 화면에 보여지는 필요한 아이템만 가져올 뿐 100개 전체를 불러오지 않습니다.

즉, 화면에 6개의 칸이 존재하고 그 칸에 계속 바꿔낍니다.

아래에서 전체의 ListView가 ViewGroup 입니다.

아래 정보가 담겨있는 한칸 한칸이 Cell 입니다.



프로젝트 생성

Custom List View 라는 이름으로 프로젝트를 생성합니다.

Layout은 Empty로 설정했습니다.


minSdkVersion은 16으로 설정했습니다.



ListView 만들기

사용할 ListView를 activity_main.xml에 만들었습니다.



Layout Resource File 만들기

item_list.xml 만들겠습니다.

제목, 글쓴이, 

Gravity는 두 가지가 있습니다.

1) Layout gravity : 하위 View(Widget) 들을 정렬한다.

2) gravity : 내용물들을 정렬한다.



VO(Value Object) 만들기

MainActivity가 있는 패키지 안에 ArticleVO 라는 이름의 java class를 만듭니다.

제목, 글쓴이, 조회수를 담을 클래스입니다.

변수들을 만들고 생성자와 Getter, Setter를 만듭니다.

생성자와 Getter, Setter는 Alt + Insert 단축키를 이용하여 만들 수 있습니다.

package com.ktds.cocomo.customlistview;

/**
 * Created by 206-032 on 2016-06-14.
 */
public class ArticleVO {

    private String subject;
    private String author;
    private int hitCoutn;

    public ArticleVO(String subject, String author, int hitCoutn) {
        this.subject = subject;
        this.author = author;
        this.hitCoutn = hitCoutn;
    }

    public String getSubject() {
        return subject;
    }

    public String getAuthor() {
        return author;
    }

    public int getHitCoutn() {
        return hitCoutn;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public void setHitCoutn(int hitCoutn) {
        this.hitCoutn = hitCoutn;
    }
}



간단한 방법

간단한 방법이지만 가장 안좋은 방법이다.

매번 convertView를 만든다.

즉, 총 10만개의 데이터가 있을 때 맨 밑으로 갔다가 다시 맨위로 올라오면 총 20만개의 convertView를 만들게된다.

메모리 낭비가 심하여 갑자기 램이 죽을 수도 있다.

객체(convertView) 생성을 최소화 시켜줄 필요가 있다.

package com.ktds.cocomo.customlistview;

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity {

    private ListView lvArticleList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        lvArticleList = (ListView) findViewById(R.id.lvArticleList);

        List articleList = new ArrayList();
        for (int i = 0; i < 300; i++) {
            articleList.add(new ArticleVO("제목입니다..." + i, "글쓴이입니다.", new Random().nextInt(9999)));
        }

        lvArticleList.setAdapter(new ArticleListViewAdapter(articleList, this));
    }

    // Ctrl + i
    private class ArticleListViewAdapter extends BaseAdapter {

        /**
         * ListView에 세팅할 Item 정보들
         */
        private List articleList;

        /**
         * ListView에 Item을 세팅할 요청자의 정보가 들어감
         */
        private Context context;

        /**
         * 생성자
         *
         * @param articleList
         * @param context
         */
        public ArticleListViewAdapter(List articleList, Context context) {
            this.articleList = articleList;
            this.context = context;
        }

        /**
         * ListView에 세팅할 아이템의 갯수
         * @return
         */
        @Override
        public int getCount() {
            return articleList.size();
        }

        /**
         * position 번째 Item 정보를 가져옴
         * @param position
         * @return
         */
        @Override
        public Object getItem(int position) {
            return articleList.get(position);
        }

        /**
         * 아이템의 index를 가져옴
         * Item index == position
         * @param position
         * @return
         */
        @Override
        public long getItemId(int position) {
            return position;
        }

        /**
         * ListView에 Item들을 세팅함
         * position 번 째 있는 아이템을 가져와서 converView에 넣은다음 parent에서 보여주면된다.
         * @param position : 현재 보여질 아이템의 인덱스, 0 ~ getCount() 까지 증가
         * @param convertView : ListView의 Item Cell(한 칸) 객체를 가져옴
         * @param parent : ListView
         * @return
         */
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            /**
             * 가장 간단한 방법
             * 사용자가 처음으로 Flicking을 할 때, 아래쪽에 만들어지는 Cell(한 칸)은 Null이다.
             */
            if( convertView == null ) {

                // Item Cell에 Layout을 적용시킬 Inflater 객체를 생성한다.
                LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);

                // Item Cell에 Layout을 적용시킨다.
                // 실제 객체는 이곳에 있다.
                convertView = inflater.inflate(R.layout.list_item, parent, false);
            }
            
            TextView tvSubject = (TextView) convertView.findViewById(R.id.tvSubject);
            TextView tvAuthor = (TextView) convertView.findViewById(R.id.tvAuthor);
            TextView tvHitCount = (TextView) convertView.findViewById(R.id.tvHitCount);

            ArticleVO article = (ArticleVO) getItem(position);
            tvSubject.setText(article.getSubject());
            tvAuthor.setText(article.getAuthor());
            tvHitCount.setText(article.getHitCoutn() + "");

            return convertView;
        }
    }
}



가장 효율적인 방법 !!

홀더패턴이라 불린다.

View 한 칸에 사용되는 것들을 홀더에 넣어놓고 convertView(한 칸)에 홀더를 setTag 시킨다.

사용할 때는 매번 생성하지 않고 convertView에 태그되어있는 홀더를 꺼내서 사용한다.

이렇게 작성하면 메모리를 거의 사용하지 않는 방법이 된다.

package com.ktds.cocomo.customlistview;

import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.ktds.cocomo.customlistview.facebook.Facebook;
import com.restfb.types.Post;

import java.util.List;

public class MainActivity extends ActionBarActivity {

    private ListView lvArticleList;
    private Facebook facebook;
    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        handler = new Handler();
        lvArticleList = (ListView) findViewById(R.id.lvArticleList);

        // this : 현재 Activity의 context
        facebook = new Facebook(this);
        facebook.auth(new Facebook.After() {
            @Override
            public void doAfter(Context context) {
                // 인증이 끝나고 타임라인 세팅하기
                setTimeline();
            }
        });
    }

    /**
     * Action Bar에 메뉴를 생성한다.
     *
     * @param menu
     * @return
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.list_menu, menu);

        // 검색 기능 활성화한다.
        SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);

        // 검색 버튼을 가져온다.
        MenuItem searchButton = menu.findItem(R.id.searchButton);

        // 검색버튼을 클릭했을 때 SearchView를 가져온다.
        SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchButton);

        // 검색 힌트를 설정한다.
        searchView.setQueryHint("검색어를 입력하세요");

        // SearchView를 검색 가능한 위젝으로 설정한다.
        searchView.setSearchableInfo( searchManager.getSearchableInfo( getComponentName() ) );

        //
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {

            /**
             * 검색 버튼을 클릭했을 때 동작하는 이벤트
             * @param s : 입력된 검색어
             * @return
             */
            @Override
            public boolean onQueryTextSubmit(String s) {

                Intent intent = new Intent(MainActivity.this, SearchActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                intent.putExtra("query", s);
                startActivity(intent);

                return false;
            }

            /**
             * 검색어를 입력할 때 동작하는 이
             * @param s
             * @return
             */
            @Override
            public boolean onQueryTextChange(String s) {
                return false;
            }
        });

        return true;
    }

    /**
     * 메뉴 아이템을 클릭했을 때 발생되는 이벤트
     *
     * @param item
     * @return
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        int id = item.getItemId();
        if (id == R.id.newPost) {
            Intent intent = new Intent(this, WritePostActivity.class);

            // 글 작성됐는지 확인하고 게시글을 업데이트 하기 위해 forResult를 이용한다.
            startActivityForResult(intent, 1000);

            // Toast.makeText(MainActivity.this, "새 글 등록 버튼을 클릭했습니다", Toast.LENGTH_SHORT).show();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * WritePostActivity에서 포스트가 작성되었으면 리스트를 갱신한다.
     * @param requestCode
     * @param resultCode
     * @param data
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 1000 && resultCode == RESULT_OK) {
            setTimeline();
        }
    }

    public void setTimeline() {
        if (facebook.isLogin()) {
            // timeline 가져오기
            facebook.getTimeLine(new Facebook.TimelineSerializable() {
                @Override
                public void serialize(final List posts) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            lvArticleList.setAdapter(new ArticleListViewAdapter(MainActivity.this, posts));
                        }
                    });
                }
            });
        }
    }

    // Ctrl + i
    private class ArticleListViewAdapter extends BaseAdapter {

        /**
         * ListView에 세팅할 Item 정보들
         */
        private List articleList;

        private Post article;

        /**
         * ListView에 Item을 세팅할 요청자의 정보가 들어감
         */
        private Context context;

        /**
         * 생성자
         *
         * @param articleList
         * @param context
         */
        public ArticleListViewAdapter(Context context, List articleList) {
            this.articleList = articleList;
            this.context = context;
        }

        /**
         * Item의 속성에 따라서 보여질 아이템 레이아웃을 정해준다.
         *
         * @param position
         * @return
         */
        @Override
        public int getItemViewType(int position) {

            /**
             * 만약, Message가 Null이 아니라면 list_item_message를 보여주고
             * 만약, Story가 Null이 아니라면 list_item_story를 보여주고
             * 만약, link가 Null이 아니라면 list_item_link를 보여준다.
             */
            article = (Post) getItem(position);

            if (article.getMessage() != null && article.getMessage().length() > 0) {
                return 0;
            } else if (article.getStory() != null && article.getStory().length() > 0) {
                return 1;
            } else if (article.getStory() != null && article.getStory().length() > 0) {
                return 2;
            } else {
                return -1;
            }
        }

        public int getLayoutType(int index) {
            if (index == 0) {
                return R.layout.list_item_message;
            } else if (index == 1) {
                return R.layout.list_item_story;
            } else if (index == 2) {
                return R.layout.list_item_link;
            } else {
                return -1;
            }
        }

        /**
         * Item Layout의 개수를 가져온다.
         *
         * @return
         */
        @Override
        public int getViewTypeCount() {
            return 3;
        }

        /**
         * ListView에 세팅할 아이템의 갯수
         *
         * @return
         */
        @Override
        public int getCount() {
            return articleList.size();
        }

        /**
         * position 번째 Item 정보를 가져옴
         *
         * @param position
         * @return
         */
        @Override
        public Object getItem(int position) {
            return articleList.get(position);
        }

        /**
         * 아이템의 index를 가져옴
         * Item index == position
         *
         * @param position
         * @return
         */
        @Override
        public long getItemId(int position) {
            return position;
        }

        /**
         * ListView에 Item들을 세팅함
         * position 번 째 있는 아이템을 가져와서 converView에 넣은다음 parent에서 보여주면된다.
         *
         * @param position    : 현재 보여질 아이템의 인덱스, 0 ~ getCount() 까지 증가
         * @param convertView : ListView의 Item Cell(한 칸) 객체를 가져옴
         * @param parent      : ListView
         * @return
         */
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            ItemHolder holder = null;
            int layoutType = getItemViewType(position);

            /**
             * 가장 효율적인 방법 !!
             * 사용자가 처음으로 Flicking을 할 때, 아래쪽에 만들어지는 Cell(한 칸)은 Null이다.
             */
            if (convertView == null) {

                // Item Cell에 Layout을 적용시킬 Inflater 객체를 생성한다.
                LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);

                /**
                 * Item Cell에 Layout을 적용시킨다.
                 * 실제 객체는 이곳에 있다.
                 */
                convertView = inflater.inflate(getLayoutType(layoutType), parent, false);
                holder = new ItemHolder();

                if (layoutType == 0) {
                    holder.tvSubject = (TextView) convertView.findViewById(R.id.tvSubject);
                    holder.tvAuthor = (TextView) convertView.findViewById(R.id.tvAuthor);
                    holder.tvHitCount = (TextView) convertView.findViewById(R.id.tvHitCount);

                    convertView.setOnClickListener(clickDetail(article.getId()));

                } else if (layoutType == 1) {
                    holder.tvSubject = (TextView) convertView.findViewById(R.id.tvSubject);
                } else if (layoutType == 2) {
                    holder.tvSubject = (TextView) convertView.findViewById(R.id.tvSubject);
                    holder.tvSubject.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(article.getLink()));
                            startActivity(intent);
                        }
                    });
                }

                // 항상 convertView는 holder를 등에 업어간다.
                convertView.setTag(holder);
            }
            /**
             * convertView != null 이면 홀더를 꺼낸다.
             */
            else {
                holder = (ItemHolder) convertView.getTag();
            }

            article = (Post) getItem(position);
            if (layoutType == 0) {
                holder.tvSubject.setText(article.getMessage());
                holder.tvAuthor.setText(article.getFrom().getName());

                if (article.getLikes() == null) {
                    holder.tvHitCount.setText("0");
                } else {
                    holder.tvHitCount.setText(article.getLikes().getData().size() + "");
                }

                convertView.setOnClickListener(clickDetail(article.getId()));

            } else if (layoutType == 1) {
                holder.tvSubject.setText(article.getStory());
            } else if (layoutType == 2) {
                holder.tvSubject.setText(article.getLink());
            }
            return convertView;
        }
    }

    private View.OnClickListener clickDetail (final String postId) {

        return new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(view.getContext(), DetailActivity.class);
                intent.putExtra("postId", postId);
                startActivity(intent);
            }
        };
    }

    /**
     * 홀더
     */
    private class ItemHolder {
        public TextView tvSubject;
        public TextView tvAuthor;
        public TextView tvHitCount;
    }
}




Serializable

리스트 뷰의 여러 정보중 하나를 클릭했을 때 해당 정보에 대한 상세정보를 보여줄 디테일 화면을 만들겠습니다.

우선 알아두어야 할 것이 Serializable입니다.

참조를 시키고싶은 클래스에 implements Serializable 해주면 메모리가 동기화된다.

두 개의 서버에서 똑같은 클래스똑같은 주소의 참조를 시킬 때 Serializable을 사용한다.

예를 들어 두 개의 Activity가 완전히 다른 메모리를 사용하고 있을 때 Activity 내에서 일정 부분을

동기화(같은 메모리) 시키고 싶을 때도 Serializable을 사용한다.

배터리와 메모리 사용에 효율적이다.

아직 Serializable에 대한 개념이 어려워서 다시 정리 후 블로깅 하겠습니다!


이제 Serializable을 이용해서,

intent putSerializableExtra를 통해 ArticleVO(객체)를 담아서 다른 Activity로 보내보겠습니다.



객체 intent로 보내기

우선, 상세 정보를 보여줄 DetailActivity를 만듭니다.



activity_detail.xml에서 상세 정보를 보여줄 화면(Layout)을 구성합니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ktds.cocomo.customlistview.DetailActivity">

<TextView
android:id="@+id/tvSubject"
android:text="Subject"
android:textSize="15dp"
android:textColor="#000000"
android:paddingLeft="5dp"
android:paddingTop="5dp"
android:paddingBottom="3dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/tvAuthor"
android:text="글쓴이"
android:paddingLeft="5dp"
android:layout_weight="9"
android:layout_width="wrap_content"
android:layout_height="match_parent" />

<TextView
android:id="@+id/tvHitCount"
android:text="999"
android:layout_weight="1"
android:gravity="right"
android:paddingRight="5dp"
android:layout_width="wrap_content"
android:layout_height="match_parent" />

</LinearLayout>

</LinearLayout>




ArticleVO에 implements Serializable 해줍니다.

동기화(메모리 공유)시켜서 해당 객체를 intent를 통해 보내서 DetailActivity에서 객체를 받아 사용할 수 있도록 준비합니다.

public class ArticleVO implements Serializable


리스트뷰에서 한 칸을 클릭했을 때, 그 칸 안에 담긴 정보를 가지고 있는 ArticleVO 객체를 putExtra를 통해 DetailActivity로 보냅니다.

        lvArticleList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view, int position, long id) {

                ArticleVO article = articleList.get(position);

                Intent intent = new Intent(view.getContext(), DetailActivity.class);
                intent.putExtra("article", article);
                startActivity(intent);
            }
        });



객체 받기

MainActivity에서 보낸 ArticleVO 객체를 DetailActivity에서 받아보겠습니다.

Serialize(동기화)된 클래스의 객체를 받을 땐 getSerializableExtra 를 이용합니다.

public class DetailActivity extends AppCompatActivity {

    private TextView tvSubject;
    private TextView tvAuthor;
    private TextView tvHitCount;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);

        tvSubject = (TextView) findViewById(R.id.tvSubject);
        tvAuthor = (TextView) findViewById(R.id.tvAuthor);
        tvHitCount = (TextView) findViewById(R.id.tvHitCount);

        Intent intent = getIntent();
        /**
         * 객체를 받아옵니다.
         */
        ArticleVO article = (ArticleVO) intent.getSerializableExtra("article");

        tvSubject.setText(article.getSubject());
        tvAuthor.setText(article.getAuthor());
        /**
         * 조회수(HitCount)는 정수형이기 때문에 [+ ""]을 통해서 문자열로 바꿔줍니다.
         */
        tvHitCount.setText(article.getHitCoutn() + "");
    }
}



디테일 화면

리스트뷰에서 4번 글을 눌러보겠습니다.


DetailActivity에서 4번 글 객체를 받아 보여줍니다.


이상으로 리스트뷰 블로깅 마치겠습니다! 감사합니다!



출처: http://cocomo.tistory.com/397?category=687308 [Cocomo Coding]
[본문링크] 안드로이드(Android) - 리스트뷰(ListView)
[1]
코멘트(이글의 트랙백 주소:/cafe/tb_receive.php?no=34706
작성자
비밀번호

 

SSISOCommunity

[이전]

Copyright byCopyright ⓒ2005, SSISO Community All Rights Reserved.